Unity的PBR扩展(三)——扩展框架

Unity的PBR扩展(三)——扩展框架

为什么要扩展PBR

基于物理的渲染(PBR, Physically based rendering)采用了物理真实的光照模型,符合自然世界的直观认知规律,近年来逐渐流行。PBR可理解为是一套渲染标准,具体实现由各大引擎自己负责,Unity的标准PBR实现为Standard,在UE4中封装为Default Lit。

标准化有利于简化美术流程,但是只使用标准PBR达不到具体项目的渲染需求,因为游戏风格差异化大多来自对渲染的自定义,比如卡通化PBR渲染。卡通渲染也叫非真实渲染(NPR, Non-Photorealistic rendering), 从名字上看NPR与PBR两者很冲突,仿佛卡通化PBR是个伪命题,但业内有两者结合的很好的例子:

异度之刃2的游戏截图

上图为NS主机游戏异度之刃2的游戏截图,角色脸部和头发都是比较卡通的,其它部分的金属质感盔甲和场景都是写实风格的。

异度之刃2在非真实建模的前提下,真实渲染和非真实渲染结合,配合后期调色,形成了游戏特有的卡渲风格。游戏中角色脸部和头发渲染这种具有PBR属性,但又风格化的渲染就属于自定义PBR。

如何扩展PBR

本文的自定义PBR是在Unity的Standard的基础上去修改和扩展。 先看Unity的Standard实现:

Unity的Standard Forward绘制调用示意图

这里列出的是Unity Standard实现的主要函数调用关系,核心PBS详细实现可参阅《PBS代码剖析》。

在此基础上去自定义,对内主要包括修改和扩展上图中的数据结构、光照模型和绘制过程。对外给用户提供可选择的Shading Model:

UE4的Shading Model和Unity扩展代码文件

上图左为UE4引擎的Shading Model,右为本文在Unity中按UE4方式扩展实现的代码文件列表,其中TT_Unity???.cginc文件为Unity内部的Unity???.cginc文件的修改扩展版本。

其中,TT_UnityStandardBRDF.cginc扩展了各个Shadering Model的实现:

 half4 SKIN_BRDF_PBS(half3 diffColor, half3 specColor, half oneMinusReflectivity, half smoothness,
  float3 normal, float3 viewDir,
  UnityLight light, UnityIndirect gi, half4 sssTex)
 {
  ...
 }
  
 half4 HAIR_BRDF_PBS (half3 diffColor, half3 specColor, half oneMinusReflectivity, half smoothness,
  float3 normal, float3 viewDir,
  UnityLight light, UnityIndirect gi, float3 tangentWorld = float3(0, 0, 1), half2 anisoCtrl = half2(1, 1))
 {
  ...
 }
  
 half4 CLEARCOAT_BRDF_PBS (half3 diffColor, half3 specColor, half oneMinusReflectivity, half smoothness,
  float3 normal, float3 viewDir,
  UnityLight light, UnityIndirect gi, float3 normal_clearcoat, UnityIndirect gi_clearcoat)
 {
  ... 
 }
  
 half4 FABRIC_BRDF_PBS (half3 diffColor, half3 specColor, half oneMinusReflectivity, half smoothness,
  float3 normal, float3 viewDir,
  UnityLight light, UnityIndirect gi)
 {
  ...
 }
 

TT_UnityStandardCore.cginc根据Shadering Model的类型选择实现:

 half4 fragForwardBaseInternal (VertexOutputForwardBase i)
 {
  ...
 #if _SKIN
  half4 c = SKIN_BRDF_PBS(s.diffColor, s.specColor, s.oneMinusReflectivity, s.smoothness, s.normalWorld, -s.eyeVec, gi.light, gi.indirect, sssTex);
 #elif _HAIR
  half4 c = HAIR_BRDF_PBS(s.diffColor, s.specColor, s.oneMinusReflectivity, s.smoothness, s.normalWorld, -s.eyeVec, gi.light, gi.indirect, s.tangentWorld, anisoMap.rg);
 #elif _CLEARCOAT
  half4 c = CLEARCOAT_BRDF_PBS(s.diffColor, s.specColor, s.oneMinusReflectivity, s.smoothness, s.normalWorld, -s.eyeVec, gi.light, gi.indirect, s.normalWorld_clearcoat, gi_clearcoat.indirect);
 #elif _FABRIC
  half4 c = FABRIC_BRDF_PBS(s.diffColor, s.specColor, s.oneMinusReflectivity, s.smoothness, s.normalWorld, -s.eyeVec, gi.light, gi.indirect);
 #else
  half4 c = UNITY_BRDF_PBS(s.diffColor, s.specColor, s.oneMinusReflectivity, s.smoothness, s.normalWorld, -s.eyeVec, gi.light, gi.indirect);
 #endif
  ...
 }
  
 ...
  
 half4 fragForwardAddInternal (VertexOutputForwardAdd i)
 {
  ...
 #if _SKIN
  half4 c = SKIN_BRDF_PBS (s.diffColor, s.specColor, s.oneMinusReflectivity, s.smoothness, s.normalWorld, -s.eyeVec, light, noIndirect, sssTex);
 #elif _HAIR
  half4 c = HAIR_BRDF_PBS(s.diffColor, s.specColor, s.oneMinusReflectivity, s.smoothness, s.normalWorld, -s.eyeVec, light, noIndirect);
 #elif _CLEARCOAT
  half4 c = CLEARCOAT_BRDF_PBS(s.diffColor, s.specColor, s.oneMinusReflectivity, s.smoothness, s.normalWorld, -s.eyeVec, light, noIndirect, s.normalWorld_clearcoat, noIndirect);
 #elif _FABRIC
  half4 c = FABRIC_BRDF_PBS (s.diffColor, s.specColor, s.oneMinusReflectivity, s.smoothness, s.normalWorld, -s.eyeVec, light, noIndirect);
 #else
  half4 c = UNITY_BRDF_PBS (s.diffColor, s.specColor, s.oneMinusReflectivity, s.smoothness, s.normalWorld, -s.eyeVec, light, noIndirect);
 #endif
  ...
 }
 

具体的shader文件去定义Shadering Model的类型,比如TT_Character_Skin.shader定义了_SKIN类型:

 SubShader
 {
  Tags{ "RenderType" = "Opaque" "PerformanceChecks" = "False" }
  LOD 300
  
  CGINCLUDE
  ...
  #define _SKIN 1
  ...
 }

Unity的原生内置着色器cginc文件在类似目录:C:\Program Files\Unity 2018.1.0b13\Editor\Data\CGIncludes\,也可到官网下载Unity安装版本对应的内置着色器代码。


下面对各个Shading Model的理论模型和具体实现依次展开介绍。

Subsurface

The Subsurface Shading Model simulates the effect of Subsurface Scattering. This is a real-world phenomenon in which light penetrates a surface and then diffuses throughout it. It can be most readily seen on such objects as ice, wax candles, and skin.
the final colour of our pixels depend is the sum of two components. The first one is the “traditional” lighting. The second one is the light contribution from a virtual light source illuminating the back of our model. This gives the impression that light from the original source actually passed through the material.
Subsurface理论模型

Subsurface模型实现核心代码:

 half4 SKIN_BRDF_PBS(half3 diffColor, half3 specColor, half oneMinusReflectivity, half smoothness,
  float3 normal, float3 viewDir,
  UnityLight light, UnityIndirect gi, half4 sssTex)
 {
  ...
  // Translucency
  float3 H = normalize(light.dir + normal * _Distortion);
  float transDot = pow(saturate(dot(viewDir, -H)), _Power) * thickness * _ThicknessScale;
  half3 lightScattering = transDot * _SubColor;
  ...
 }
Subsurface模型实现效果图


Skin

The Preintegrated Skin Shading Model is very similar in nature to the Subsurface model, but geared toward low performance cost skin rendering on human characters.
the higher curvature on the nose creates stronger incident light gradient, Which will result in a lot more visible scattering.
皮肤的Ramp图

Skin模型实现核心代码:

 inline fixed4 SKIN_BRDF_PBS(half3 diffColor, half3 specColor, half oneMinusReflectivity, half smoothness,
  float3 normal, float3 viewDir,
  UnityLight light, UnityIndirect gi, 
  half4 sssTex)
 {
  ...
  // Skin Lighting
  float2 brdfUV;
  // Half-Lambert lighting value based on blurred normals.
  brdfUV.x = dotNL * 0.5 + 0.5;
  brdfUV.y = curvature;
  // Curvature amount. Multiplied by light's luminosity so brighter light = more scattering. 
  half3 diffuseTerm = diffColor * light.color * tex2D( _BRDFTex, brdfUV ).rgb;
  ...
 


Skin模型实现效果图


ClearCoat

The Clear Coat Shading Model can be used to better simulate multilayer materials that have a thin translucent layer of film over the surface of a standard material. In addition to this, the Clear Coat Shading Model can also be used with either a metal or nonmetal surfaces. In fact, it was specifically designed to model this second class of smooth colored films over a non-colored metal. Some examples include acrylic or lacquer clear coats, and colored films over metals such as soda cans and car paint.
Paint Layers示意图

ClearCoat模型实现核心代码:

 half4 fragForwardBaseInternal (VertexOutputForwardBase i)
 {
  ...
 #if _CLEARCOAT
  FragmentCommonData s_clearcoat = (FragmentCommonData) 0;
  s_clearcoat.specColor = _ReflectionSpecular.rgb;
  s_clearcoat.smoothness = _ReflectionGlossiness;
  s_clearcoat.normalWorld = s.normalWorld_clearcoat;
  s_clearcoat.eyeVec = s.eyeVec;
  s_clearcoat.posWorld = s.posWorld;
  UnityGI gi_clearcoat = FragmentGI(s_clearcoat, occlusion, i.ambientOrLightmapUV, atten, mainLight);
  
  half4 c = CLEARCOAT_BRDF_PBS(s.diffColor, s.specColor, s.oneMinusReflectivity, s.smoothness, s.normalWorld, -s.eyeVec, gi.light, gi.indirect, s.normalWorld_clearcoat, gi_clearcoat.indirect);
 #endif 
  ...
 }
 half4 CLEARCOAT_BRDF_PBS (half3 diffColor, half3 specColor, half oneMinusReflectivity, half smoothness,
  float3 normal, float3 viewDir,
  UnityLight light, UnityIndirect gi, float3 normal_clearcoat, UnityIndirect gi_clearcoat)
 {
  half4 c = BRDF1_Unity_PBS(diffColor, specColor, oneMinusReflectivity, smoothness, normal, viewDir, light, gi);
  
  // SPECULAR & SMOOTHNES
  diffColor = 0;
  specColor = _ReflectionSpecular.rgb;
  smoothness = _ReflectionGlossiness;
  oneMinusReflectivity = 1 - SpecularStrength(specColor);
  c += BRDF1_Unity_PBS(diffColor, specColor, oneMinusReflectivity, smoothness, normal_clearcoat, viewDir, light, gi_clearcoat);
  
  return c;
 }
 


ClearCoat模型实现效果图


Cloth

For fabrics, like black velvet, the most distinguishing features are due to rim lighting (both forward and backward scattering). If the light is in the same direction as the viewer then specular contributes most towards the edge of the object due to backscattering and how the fabric is constructed. Tiny fibers are attached to the surface so that they try to stand up straight. When the light and view direction are aligned the light will backscatter when the surface normal is 90 degrees from the light or view direction. Additionally, if the light is behind the objects the fibers will forward scatter light through giving a nice rim light effect.
理想反射与实际织物的反射对比

Cloth模型实现核心代码:

 inline float FabricD (float NdotH, float roughness)
 {
  return 0.96 * pow(1 - NdotH, 2) + 0.057; 
 }
  
 inline half FabricScatterFresnelLerp(half nv, half scale)
 {
  half t0 = Pow4 (1 - nv); 
  half t1 = 0.4 * (1 - nv);
  return (t1 - t0) * scale + t0;
 }
  
 half4 FABRIC_BRDF_PBS (half3 diffColor, half3 specColor, half oneMinusReflectivity, half smoothness,
  float3 normal, float3 viewDir,
  UnityLight light, UnityIndirect gi)
 {
  ...
  float D = FabricD (nh, roughness);
  ...
  half3 color = diffColor * (gi.diffuse + light.color * diffuseTerm)
  + specularTerm * light.color * FresnelTerm (specColor, lh)
  + _FabricScatterColor * (nl*0.5 + 0.5) * FabricScatterFresnelLerp(nv, _FabricScatterScale);
  ...
 }
 
Cloth模型实现效果图


Hair

我们日常生活中有很多物体呈现各向异性反射效果。比如:拉丝金属,毛发,光碟等。一般这种反射效果是由物体表面的微表面特性导致的:物体表面主要由大量的方向一致的细长划痕或纤维微表面组成。 比如,拉丝金属物件表面由大量平行的丝状划痕组成;光碟的表面由一圈一圈的环形细小轨道(用于存放数据)组成;头发的表面由大量的头发丝组成等。沿着这些划痕或纤维的法线分布不同于通常的垂直于表面的法线分布, 使得物体整体的光照反射表现呈现各向异性。
各项异性模型

Hair模型实现核心代码:

 inline half AnisoDCore(half smoothness, half3 normalWorld, half3 tangentWorld, half3 halfDir, half nh, half D, half gloss, half spec, half mask)
 {
  half3 Y = cross(normalWorld, tangentWorld);
  half RoughnessX = SmoothnessToRoughness(saturate(smoothness * gloss));
  RoughnessX += !RoughnessX * 1e-4f;
   half mx = RoughnessX * RoughnessX;
  half XdotH = dot(tangentWorld, halfDir);
  half YdotH = dot(Y, halfDir);
  half d = XdotH * XdotH / (mx * mx) + YdotH * YdotH + nh * nh;
  d += !d * 1e-4f;
  half Da = 1 / (UNITY_PI * mx * d * d);
  D = lerp(Da, D, mask);
  D *= lerp(spec, 1, mask);
  
  return D;
 }
  
 inline half3 JitterTangent(half3 T, half3 N, float shift)
 {
  half3 shiftedT = T + shift * N;
  return normalize(shiftedT);
 }
  
 inline half AnisoD(half smoothness, half3 normalWorld, half3 tangentWorld, half3 halfDir, half nh, half D, half2 anisoCtrl)
 {
  half jitter = anisoCtrl.r;
  half mask = anisoCtrl.g;
  
  half3 tangentWorld1 = JitterTangent(tangentWorld, normalWorld, 0 + _TangentShift1);
  half AnisoDLow = AnisoDCore(smoothness, normalWorld, tangentWorld1, halfDir, nh, D, _AnisoGloss1, _AnisoSpec1, mask);
  
  half3 tangentWorld2 = JitterTangent(tangentWorld, normalWorld, jitter + _TangentShift2);
  half AnisoDHigh = AnisoDCore(smoothness, normalWorld, tangentWorld2, halfDir, nh, D, _AnisoGloss2, _AnisoSpec2, mask); 
  
  return AnisoDLow + AnisoDHigh;
 }
  
 half4 HAIR_BRDF_PBS (half3 diffColor, half3 specColor, half oneMinusReflectivity, half smoothness,
  float3 normal, float3 viewDir,
  UnityLight light, UnityIndirect gi, float3 tangentWorld = float3(0, 0, 1), half2 anisoCtrl = half2(1, 1))
 {
  ...
   float D = GGXTerm(nh, roughness);
  D = AnisoD(smoothness, normal, tangentWorld, halfDir, nh, D, anisoCtrl);
  ...
 }
 
Hair模型实现效果图

完整代码见:

chenyong2github/ExtendStandard github.com图标

Further

目前的代码框架扩展实现了上面列举的几种常见的Shading Model,未来的工作就是在这些Shading Model的基础上去风格化,或者扩展新的Shading Model。


另外,说明一下从源码级别自定义PBR的意义:目前的材质节点编辑器,比如Amplify Shader Editor,生成的是Surface中间代码,内部走的是Standard流程;Shader Forge(目前已停更),生成的代码调用的也是内置的Standard接口;Unity官方的SRP配套使用的Shader Graph只支持Standard材质。So~~~!


参考文献:
marmoset.co/posts/physi

alanzucconi.com/2017/08

docs.unrealengine.com/e

web.engr.oregonstate.edu

编辑于 2018-12-25
想来知乎工作?请发送邮件到 jobs@zhihu.com